/*
 * @Author: Wangjun
 * @Date: 2023-08-21 13:30:34
 * @LastEditTime: 2024-12-19 09:28:47
 * @LastEditors: wangjun haodreams@163.com
 * @Description:
 * @FilePath: \xdasd:\go\src\gitee.com\haodreams\libs\easy\pack.go
 * hnxr
 */
package pack

import (
	"encoding/binary"
	"errors"
	"io"
	"net"
	"time"
)

// 错误超时
var (
	ErrTimeout  = errors.New("timeout")
	ErrBufSmall = errors.New("buffer is small")
)

// 读和写只能单线程运行
type Pack struct {
	conn          io.ReadWriteCloser
	nRead         uint64 //读取的字节数
	nWrite        uint64 //写的字节数
	msReadTimeout int64  //读取数据超时事件,毫秒
	readHead      [4]byte
	writeHead     [4]byte
}

func NewPack(conn io.ReadWriteCloser, msReadTimeout int64) *Pack {
	pack := new(Pack)
	pack.msReadTimeout = msReadTimeout
	pack.conn = conn
	return pack
}

// 已接收字节数
func (m *Pack) TotalRead() uint64 {
	return m.nRead
}

// 已发送字节数
func (m *Pack) TotalWrite() uint64 {
	return m.nWrite
}

/**
 * @description: 读取数据时自动去掉报文头
 * @param {[]byte} p
 * @return {*}
 */
func (m *Pack) ReadMessage() (data []byte, err error) {
	_, err = ReadTimeout(m.conn, m.readHead[:], int(m.msReadTimeout))
	if err != nil {
		return
	}
	m.nRead += 4
	l := int(binary.BigEndian.Uint32(m.readHead[:]))
	data = make([]byte, l)
	m.nRead += uint64(l)

	_, err = ReadTimeout(m.conn, data, int(m.msReadTimeout))
	if err != nil {
		return
	}
	return
}

/**
 * @description: 写入数据时自动包装写入长度
 * @param {[]byte} p
 * @return {*}
 */
func (m *Pack) WriteMessage(p []byte) (err error) {
	_, err = m.Write(p)
	return err
}

/**
 * @description: 读取数据时自动去掉报文头
 * @param {[]byte} p
 * @return {*}
 */
func (m *Pack) Read(p []byte) (n int, err error) {
	n, err = ReadTimeout(m.conn, m.readHead[:], int(m.msReadTimeout))
	if err != nil {
		return
	}
	m.nRead += uint64(n)
	l := int(binary.BigEndian.Uint32(m.readHead[:]))
	if l >= len(p) {
		err = ErrBufSmall
		return
	}

	n, err = ReadTimeout(m.conn, p[:l], int(m.msReadTimeout))
	m.nRead += uint64(n)
	return
}

/**
 * @description: 写入数据时自动包装写入长度
 * @param {[]byte} p
 * @return {*}
 */
func (m *Pack) Write(p []byte) (n int, err error) {
	binary.BigEndian.PutUint32(m.writeHead[:], uint32(len(p)))
	n, err = m.conn.Write(m.writeHead[:])
	if err != nil {
		return
	}
	m.nWrite += uint64(n)

	n, err = m.conn.Write(p)
	m.nWrite += uint64(n)
	return
}

func (m *Pack) Close() (err error) {
	return m.conn.Close()
}

/**
 * @description: 带超时时间限制的读取
 * @param {io.Reader} conn
 * @param {[]byte} data
 * @param {int} mstimeout
 * @return {*}
 */
func ReadTimeout(conn io.Reader, data []byte, mstimeout int) (num int, err error) {
	length := len(data)
	num = 0

	if tcpconn, ok := conn.(*net.TCPConn); ok {
		err = tcpconn.SetReadDeadline(time.Now().Add(time.Millisecond * time.Duration(mstimeout)))
		if err != nil {
			return
		}
		for num < length {
			n, err := tcpconn.Read(data[num:]) ///*read(tcpconn, data[num:])*/
			if err != nil {
				return num, err
			}
			if n > 0 {
				tcpconn.SetReadDeadline(time.Now().Add(time.Millisecond * time.Duration(mstimeout)))
				num += n
			}
		}
	} else {
		n := 0
		t := time.Now()
		for num < length && mstimeout > 0 {
			n, err = conn.Read(data[num:])
			if err != nil {
				return num, err
			}
			num += n
			if n == 0 {
				t1 := int(time.Since(t) / time.Millisecond)
				if t1 >= mstimeout {
					err = ErrTimeout
					break
				} else if t1+50 > mstimeout {
					time.Sleep(time.Duration(mstimeout-t1) * time.Millisecond)
				} else {
					time.Sleep(time.Millisecond * 50)
				}
				continue
			}
		}
	}
	return
}

//Read ...
/*接收指定的字节数, 当没有错误发生时, num = len(data)
* 当有错误发生是num为实际接收的字节数
 */
func Read(conn io.Reader, data []byte) (num int, err error) {
	length := len(data)
	num = 0
	for num < length {
		n, err := conn.Read(data[num:])
		if err != nil {
			return num, err
		}
		num += n
	}
	return
}
